package generator;

import generator.addons.SimpleDataGenerator;
import generator.collections.AbstractTable;
import generator.collections.AccomodationTable;
import generator.collections.AttractionAvailabilityTable;
import generator.collections.AttractionTable;
import generator.collections.CountryTable;
import generator.collections.CustomerTable;
import generator.collections.InvoiceTable;
import generator.collections.OfferAttractionReservationTable;
import generator.collections.OfferAttractionTable;
import generator.collections.OfferTable;
import generator.collections.PaymentFormTable;
import generator.collections.PaymentTable;
import generator.collections.ReservationTable;
import generator.collections.ReservationTravellerTable;
import generator.collections.TransportTable;
import generator.collections.TravellerTable;
import generator.statements.Accomodation;
import generator.statements.Attraction;
import generator.statements.AttractionAvailability;
import generator.statements.Country;
import generator.statements.Customer;
import generator.statements.Invoice;
import generator.statements.Offer;
import generator.statements.OfferAttraction;
import generator.statements.OfferAttractionReservation;
import generator.statements.Payment;
import generator.statements.PaymentForm;
import generator.statements.Reservation;
import generator.statements.Reservation;
import generator.statements.ReservationTraveller;
import generator.statements.Statement;
import generator.statements.Transport;
import generator.statements.Traveller;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.sql.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Random;
import java.util.Vector;

/**
 * SQL generator class.
 *
 * @author Necro
 * @author Marek Snopkowski <snpy@snpy.info>
 * @version 12.04.2008
 */
public class Generator
{
    public static final Date NOW = new Date((new java.util.Date()).getTime());

    public enum TableType
    {
        ATTRACTION_AVAILABILITY,
        ACCOMODATION,
        ATTRACTION,
        OFFER,
        OFFER_ATTRACTION,
        OFFER_ATTRACTION_RESERVATION,
        COUNTRY,
        CUSTOMER,
        RESERVATION,
        TRANSPORT,
        TRAVELLER,
        PAYMENT,
        INVOICE,
        RESERVATION_TRAVELLER,
        PAYMENT_FORM;
    }
    HashMap<Generator.TableType, AbstractTable> tables =
            new HashMap<Generator.TableType, AbstractTable>(15);
    Vector<AbstractTable> tablesInOrder = new Vector<AbstractTable>(15);
    public static void main(String[] args) throws FileNotFoundException
    {
        BufferedWriter singleOutput, mainOutput;
        Generator main = new Generator();
        
        main.initialize();
        try
        {
            File atomized = new File("atomized");
            atomized.mkdir();

            mainOutput = new BufferedWriter(
                    new OutputStreamWriter(
                        new FileOutputStream("generated_data.sql")));
             
            mainOutput.write("USE [company]\nEXEC clear_database\n");
            mainOutput.write("PRINT 'Zaczynamy wczytywać dane:'\nBEGIN TRANSACTION");
            for (AbstractTable<Statement> table : main.tablesInOrder)
            {
                System.out.print("generating " + table + " table..");

                singleOutput = new BufferedWriter(
                        new OutputStreamWriter(
                            new FileOutputStream(
                                String.format("atomized/%s.sql", table))));
                String common = String.format("-- WYGENEROWANE DANE DLA %s\n", table);
                common += String.format("SET IDENTITY_INSERT [%s] ON\n", table);
                common += "SET NOCOUNT ON\n";
                singleOutput.write(common);
                mainOutput.write(String.format("\n\n%sPRINT ' -> %s.. OK'\n", common, table));

                for (Statement stmt : table)
                {
                    singleOutput.write("" + stmt);
                    mainOutput.write("" + stmt);
                }
                
                common = String.format("SET IDENTITY_INSERT [%s] OFF\n", table);
                common += "SET NOCOUNT OFF\n";
                singleOutput.write(common);
                mainOutput.write(common);
                
                singleOutput.close();

                System.out.println("OK");
            }
            mainOutput.write("\n\nCOMMIT TRANSACTION\n");
            mainOutput.write("PRINT 'Zakonczono wczytywanie danych.'\n");
            mainOutput.close();
        } 
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    public void initialize() //inicjalizacja tabel
    {
        tables.put(TableType.ATTRACTION_AVAILABILITY, AttractionAvailabilityTable.getInstance());
        tables.put(TableType.ACCOMODATION, AccomodationTable.getInstance());
        tables.put(TableType.ATTRACTION, AttractionTable.getInstance());
        tables.put(TableType.OFFER, OfferTable.getInstance());
        tables.put(TableType.COUNTRY, CountryTable.getInstance());
        tables.put(TableType.CUSTOMER, CustomerTable.getInstance());
        tables.put(TableType.TRANSPORT, TransportTable.getInstance());
        tables.put(TableType.PAYMENT_FORM, PaymentFormTable.getInstance());
        tables.put(TableType.OFFER_ATTRACTION, OfferAttractionTable.getInstance());
        tables.put(TableType.RESERVATION, ReservationTable.getInstance());
        tables.put(TableType.TRAVELLER, TravellerTable.getInstance());
        tables.put(TableType.RESERVATION_TRAVELLER, ReservationTravellerTable.getInstance());
        tables.put(TableType.INVOICE, InvoiceTable.getInstance());
        tables.put(TableType.PAYMENT, PaymentTable.getInstance());
        tables.put(TableType.OFFER_ATTRACTION_RESERVATION, OfferAttractionReservationTable.getInstance());

        //generowanie Country, PaymentForm, Transport
        prepareCountryTable();
        preparePaymentFormTable();
        prepareTransportTable();

        prepareOffersAndAccomodations(100, 3);
        prepareAttractionsAndTheirAvailability(300);
        prepareCustomers(1000);
        prepareReservationsAndTravellers(500, 7);
        prepareAttractionsForReservations(3);
        preparePayment();
        
        tablesInOrder.add(tables.get(TableType.COUNTRY));
        tablesInOrder.add(tables.get(TableType.TRANSPORT));
        tablesInOrder.add(tables.get(TableType.ACCOMODATION));
        tablesInOrder.add(tables.get(TableType.OFFER));
        tablesInOrder.add(tables.get(TableType.ATTRACTION));
        tablesInOrder.add(tables.get(TableType.ATTRACTION_AVAILABILITY));
        tablesInOrder.add(tables.get(TableType.OFFER_ATTRACTION));
        tablesInOrder.add(tables.get(TableType.CUSTOMER));
        tablesInOrder.add(tables.get(TableType.TRAVELLER));
        tablesInOrder.add(tables.get(TableType.PAYMENT_FORM));
        tablesInOrder.add(tables.get(TableType.RESERVATION));
        tablesInOrder.add(tables.get(TableType.PAYMENT));
        tablesInOrder.add(tables.get(TableType.INVOICE));
        tablesInOrder.add(tables.get(TableType.RESERVATION_TRAVELLER));
        tablesInOrder.add(tables.get(TableType.OFFER_ATTRACTION_RESERVATION));
    }

    private void prepareCountryTable()
    {
        for (int i = 0; i < Country.getTypesCount(); ++i)
        {
            tables.get(TableType.COUNTRY).insert(Country.get(i));
        }
    }

    public void preparePaymentFormTable()
    {
        for (int i = 0; i < PaymentForm.getTypeCount(); ++i)
        {
            tables.get(TableType.PAYMENT_FORM).insert(PaymentForm.get(i));
        }
    }

    public void prepareTransportTable()
    {
        for (int i = 0; i < Transport.getTypeCount(); ++i)
        {
            tables.get(TableType.TRANSPORT).insert(Transport.get(i));
        }
    }

    private void prepareOffersAndAccomodations(int accomodationCount, int maxOffersPerAccomodation)
    {
        Random rand = new Random();
        for (int h = accomodationCount; h > 0; --h)
        {
            // akomodacje
            Country country = (Country) tables.get(TableType.COUNTRY).selectRandom();
            Accomodation accomodation = Accomodation.getRand(country);
            tables.get(TableType.ACCOMODATION).insert(accomodation);

            //dla kazdej akomodacji generujemy kilka (max 3) ofert
            for (int i = rand.nextInt(maxOffersPerAccomodation); i > 0; --i) //generowanie Offers
            {
                // oferty
                Offer offer = Offer.genRand(accomodation, (Transport) tables.get(TableType.TRANSPORT).selectRandom());
                tables.get(TableType.OFFER).insert(offer);
            }
        }//akomodacje - end of
    }

    /**
     * Tutaj rozlosowywuja dana liczbe atrakcji miedzy ofertami.
     * getRand dba o to, zeby np. we Francji nie mozna bylo zwiedzac piramid. 
     * Dodatkowo do kazdej atrakcji generowana jest jedna informacja 
     *   w postaci attraction_availability.
     * 
     */
    private void prepareAttractionsAndTheirAvailability(int attractionCount)
    {
        for (int h = attractionCount; h > 0; h--)
        {
            Offer offer = (Offer) tables.get(TableType.OFFER).selectRandom();

            Attraction attraction = Attraction.getRand(
                    offer.getAccomodation().getCountry(),
                    offer.getPlacesLimit());
            tables.get(TableType.ATTRACTION).insert(attraction);

            OfferAttraction offer_attraction = new OfferAttraction(attraction, offer);

            tables.get(TableType.OFFER_ATTRACTION).insert(offer_attraction);
            // generowanie dyspozycyjnosci atrakcji, dla danej atrakcji
            AttractionAvailability attraction_availability = AttractionAvailability.genRand(attraction);
            tables.get(TableType.ATTRACTION_AVAILABILITY).insert(attraction_availability);
        }
    }

    //Tworzenie Customers i Reservation
    public void prepareCustomers(int customersCount)
    {
        for (int h = customersCount; h > 0; h--)
        {
            Customer customer = Customer.getRand();
            tables.get(TableType.CUSTOMER).insert(customer);
        }
    }

    public void prepareReservationsAndTravellers(int reservationCount, int maxTravellersForReservation)
    {
        for (int h = reservationCount; h > 0; h--)
        {
            Random rand = new Random();

            Offer offer = (Offer) tables.get(TableType.OFFER).selectRandom();
            Customer customer = (Customer) tables.get(TableType.CUSTOMER).selectRandom();
            PaymentForm paymentForm = (PaymentForm) tables.get(TableType.PAYMENT_FORM).selectRandom();

            Date reservationDate;
            Date tmp = offer.getStartDate();
            GregorianCalendar gc = new GregorianCalendar(tmp.getYear(), tmp.getMonth(), tmp.getDay());
            gc.add(GregorianCalendar.DATE, -14);
            tmp = new Date(gc.getTime().getYear(), gc.getTime().getMonth(), gc.getTime().getDay());

            do
            {
                reservationDate = SimpleDataGenerator.getDate();
            } while (reservationDate.before(tmp) || reservationDate.before(NOW));


            int cancelled = (rand.nextInt(10) < 2) ? 1 : 0;

            int expiresAfter = 14; // tu mozna wylosowac, chociaz nie wiem czy jest sens


            float discount = 0;
            if (rand.nextInt(100) < 20)
            {
                discount = SimpleDataGenerator.getMoneyTiny();
            }
            Reservation reservation = new Reservation(offer, customer, paymentForm, 0, reservationDate,
                    expiresAfter, discount, cancelled);

            tables.get(TableType.RESERVATION).insert(reservation);

            int freePlaces = offer.getPlacesLimit();

            //Generowanie Travellers !!! KONFLIKTY KLUCZY
            if (rand.nextInt(100) < 80)
            {
                freePlaces--;

                Traveller customerTraveller = Traveller.getRand(customer);
                tables.get(TableType.TRAVELLER).insert(customerTraveller);
                tables.get(TableType.RESERVATION_TRAVELLER).insert(
                        new ReservationTraveller(reservation, customerTraveller));
            }
            for (int k = rand.nextInt(maxTravellersForReservation) + 1; k > 0; k--)
            {
                if (freePlaces == 0) //wycieczka pelna.. koniec generowania miejsc
                {
                    break;
                }
                freePlaces--;

                Traveller traveller = Traveller.getRand();
                tables.get(TableType.TRAVELLER).insert(traveller);
                tables.get(TableType.RESERVATION_TRAVELLER).insert(
                        new ReservationTraveller(reservation, traveller));
            }
        }
    }
    
    //@todo procedurka powielajaca koszt w zaleznosci od tego ilu podroznych bierze udzial w wycieczce
    
    public void prepareAttractionsForReservations(int maxAttractionsForReservation){
        Random rand = new Random();
        for (Object tmp : tables.get(TableType.RESERVATION))
        {

            Reservation reserv = (Reservation) tmp;
            int attractionsCount = rand.nextInt(maxAttractionsForReservation);
            
            for (Object tmpAttr : tables.get(TableType.OFFER_ATTRACTION))
            {
                if(attractionsCount == 0)
                    break;
                
                OfferAttraction offer_attraction = (OfferAttraction) tmpAttr;
                if (offer_attraction.getOffer().getId() == reserv.getOffer().getId())
                {
                    attractionsCount--;
                    reserv.increasePrice(offer_attraction.getAttraction().getPrice());
                    OfferAttractionReservation newOAR = new OfferAttractionReservation(offer_attraction.getAttraction(),
                            offer_attraction.getOffer(),reserv);
                    tables.get(TableType.OFFER_ATTRACTION_RESERVATION).insert(newOAR);
                }
            }
        }
        
    }
    
    public void preparePayment()
    {
        Random rand = new Random();
        int maxPaymentsForReservation = 3;
        for (Object tmp : tables.get(TableType.RESERVATION))
        {
            Payment lastPayment=null; //potrzebne do invoice'a
            Reservation reserv = (Reservation) tmp;

            //upraszczam losowanie, w przypadku gdy wycieczka juĹĽ sie odbyla, lub sie odbywa
            //oplacana jest losowÄ… 1-3 iloscia rownych wplat
            if (reserv.getOffer().getStartDate().before(NOW))
            {
                //pomijamy jeĹĽeli rezerwacja anulowana
                if (reserv.getCancelled() == 1)
                    continue;

                int paymentsCount = rand.nextInt(maxPaymentsForReservation) + 1;
                float paymentCharge = reserv.getToPay() / paymentsCount;

                for (int h = paymentsCount; h > 0; h--)
                {
                    //data wplaty musi byc wczesniejsza niz data wycieczki
                    Date paymentDate;
                    do
                    {
                        paymentDate = SimpleDataGenerator.getDate();
                    } while (paymentDate.before(reserv.getOffer().getStartDate()));

                    Payment newPayment = new Payment(reserv, paymentDate, paymentCharge);
                    tables.get(TableType.PAYMENT).insert(newPayment);
                    reserv.charge(paymentCharge);
                    lastPayment = newPayment;
                }

            } else {
                int paymentsCount = rand.nextInt(maxPaymentsForReservation) + 1;

                for (int h = paymentsCount; h > 0; h--)
                {
                    float paymentCharge = SimpleDataGenerator.getMoneyLow();
                    if (reserv.getToPay() < paymentCharge)
                    {
                        //data wplaty musi byÄ‡ wczesniejsza niz data obecna
                        Date paymentDate;
                        do {
                            paymentDate = SimpleDataGenerator.getDate();
                        } while (paymentDate.before(NOW));
                        
                        Payment newPayment = new Payment(reserv, paymentDate, paymentCharge);
                        tables.get(TableType.PAYMENT).insert(newPayment);
                        reserv.charge(paymentCharge);
                        lastPayment = newPayment;
                    }
                }
            }
            
            if(lastPayment != null){
              
                Invoice inv = new Invoice(InvoiceTable.autoincrement(),reserv,lastPayment.getDate(),
                    lastPayment.getCharge(),0,(PaymentForm) tables.get(TableType.PAYMENT_FORM).select(1),lastPayment.getDate());
                tables.get(TableType.INVOICE).insert(inv);
            }
        }
    }
}
